#include "tcp.h"
#include <stdio.h>
#include <stdint.h>
#include <string>
#include <cstring>
#include <utils/logger.h>
#include <thread>
#include <sys/time.h>

namespace communication {

Tcp::Tcp() : lockSsl_(true)
{
    // LOG_ENABLE(false);
}

Tcp::~Tcp()
{
    destroyTcp();
}

std::string Tcp::name() const
{
    return std::string("tcp");
}

bool Tcp::Connect(const std::string &host, int port)
{
	HTELINK_LOG_INFO("lock server_host:%s, port:%d\r\n", host.c_str(), port);
	AutoLock lock(lockSsl_);

    if (!initTcp()) {
        return false;
    }
    host_ = host;
    port_ = port;

    int ret = 0;
    uint32_t flags;
    connect_ = false;
	HTELINK_LOG_DEBUG(
		"connect server_host:%s, port:%d\r\n", host.c_str(), port);
	if ((ret = mbedtls_net_connect(&serverFd_, host.c_str(), std::to_string(port).c_str(), MBEDTLS_NET_PROTO_TCP)) !=
        0) {
        char error[256];
        mbedtls_strerror(ret, error, 256);
        HTELINK_LOG_ERR("mbedtls_net_connect failed, %d, %s\n", ret, error);
        return false;
    }
	HTELINK_LOG_INFO("connect success\n\n");

	connect_ = true;
    return true;
}

void Tcp::DisConnect()
{
    destroyTcp();
}

int Tcp::SendWithReply(const char *data, int len, char reply[])
{
    AutoLock lock(lockSsl_);
    if (!SendData(data, len)) {
        return 0;
    }
    return RecvData(reply);
}

int32_t Tcp::SendData(const char *data, int32_t len)
{
    AutoLock lock(lockSsl_);
    int ret = 0;
    int times = 0;
    while ((ret = mbedtls_net_send(&serverFd_, (unsigned char*)data, len)) <= 0  && ++times < 100) {
        /* code */
        if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
            continue;
        }
        char error[256];
        mbedtls_strerror(ret, error, 256);
        HTELINK_LOG_ERR("mbedtls_net_send failed, %d, %s\n", ret, error);
        return ret;
    }
    return ret;
}

int Tcp::RecvData(char reply[], int max, int timeout)
{
    const int TLS_MAX_RECV_SIZE = 8;
    AutoLock lock(lockSsl_);

    timeval t1, t2;
    int recvLen = 0;

    gettimeofday(&t1, nullptr);
    while (true) {
        int ret = 0;
        gettimeofday(&t2, nullptr);
        double msecs = (t2.tv_sec-t1.tv_sec)*1000.0 + (t2.tv_usec-t1.tv_usec)/1000.0;
        if(msecs > timeout) {
            break;
        }

        if (max == -1) {
            ret = mbedtls_net_recv_timeout(&serverFd_, (unsigned char *)&reply[recvLen], TLS_MAX_RECV_SIZE, 50);
        } else {
            if (recvLen >= max) {
                break;
            }
            ret = mbedtls_net_recv_timeout(&serverFd_, (unsigned char *)&reply[recvLen], max-recvLen, 50);
        }

        if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_TIMEOUT) {
            continue;
        }

        if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
            HTELINK_LOG_ERR("mbedtls_net_recv close notify\n");
            mbedtls_net_close(&serverFd_);
            connect_ = false;
            break;
        }

        if (ret < 0) {
            char error[256];
            mbedtls_strerror(ret, error, 256);
            HTELINK_LOG_ERR("failed\n  ! mbedtls_net_recv returned %d, %s\n\n", ret, error);
            mbedtls_net_close(&serverFd_);
            connect_ = false;
            break;
        }

        if (ret == 0) {
            break;
        }
        recvLen += ret;
    }

    return recvLen;
}

void Tcp::TcpDebug(void *ctx, int level,
    const char *file, int line, const char *str) {
    ((void) level);
    // fprintf(stdout, "%s:%04d: %s", file, line, str );
    // fflush(stdout);
}

bool Tcp::initTcp()
{
    AutoLock lock(lockSsl_);
    int ret = 1, len;

    connect_ = false;
    init_ = false;
	/*
	 * 0. Initialize the RNG and the session data
	 */
	mbedtls_net_init(&serverFd_);

#ifdef MBEDTLS_DEBUG_C
    mbedtls_debug_set_threshold(1);
#endif
    init_ = true;
    return true;
}

void Tcp::destroyTcp()
{
    AutoLock lock(lockSsl_);
    if (init_ == false) {
        return;
    }
	mbedtls_net_free(&serverFd_);
    connect_ = false;
    init_ = false;
}
}
